package tcln.gxtx.client.widget.grid.renderer.widget;

import java.util.ArrayList;

import tcln.gxtx.client.widget.treegrid.AbstractTreeGrid;
import tcln.gxtx.client.widget.treegrid.model.AbstractTreeModel;

import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.FieldEvent;
import com.extjs.gxt.ui.client.event.GridEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.store.Record;
import com.extjs.gxt.ui.client.widget.Component;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.Grid;

/**
 * Base class for all grid cell renderer supporting widgets such as checkbox, dropdown, text field...
 * 
 * @param <M>
 *            model data type.
 * @author Truong Chau Lien Ngoc (lienngoc@gmail.com)
 */
public abstract class AbstractWidgetGridCellRenderer<M extends AbstractTreeModel> implements
        WidgetGridCellRenderer<M> {

    protected Grid<M> ownerGrid;
    protected ColumnConfig ownerColumn;
    protected AbstractTreeGrid<M> ownerTree;

    protected ArrayList<Listener<GridEvent<M>>> changeEventListener = new ArrayList<Listener<GridEvent<M>>>();
    protected boolean componentPluginInitialized;

    protected boolean inContextualRenderer;
    protected ContextualWidgetGridCellRenderer<M> contextualRenderer;

    /**
     * @param ownerColumn
     */
    public AbstractWidgetGridCellRenderer(ColumnConfig ownerColumn) {
        this.ownerColumn = ownerColumn;
    }

    /**
     * @param ownerColumn
     * @param inContextualRenderer
     * @param contextualRenderer
     */
    public AbstractWidgetGridCellRenderer(ColumnConfig ownerColumn, boolean inContextualRenderer,
            ContextualWidgetGridCellRenderer<M> contextualRenderer) {
        this(ownerColumn);
        this.inContextualRenderer = inContextualRenderer;
        if (inContextualRenderer) {
            this.contextualRenderer = contextualRenderer;
        }
    }

    /**
     * {@inheritDoc}
     */
    public ColumnConfig getOwnerColumn() {
        return ownerColumn;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isInitialized() {
        return componentPluginInitialized;
    }

    /**
     * {@inheritDoc}
     */
    public void setOwnerTree(AbstractTreeGrid<M> ownerTree) {
        this.ownerTree = ownerTree;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isInContextualRenderer() {
        return inContextualRenderer;
    }

    /**
     * {@inheritDoc}
     */
    public void addChangeListener(Listener<GridEvent<M>> listener) {
        if (listener != null) {
            changeEventListener.add(listener);
        }
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public void init(Component component) {
        this.ownerGrid = (Grid<M>) component;

        // if this renderer is a part of a contextual renderer (which supports render-by-context), event handling has
        // been done in the container level -> don't need to handle here. Otherwise, event handling is necessary.
        if (!isInContextualRenderer()) {
            ownerGrid.addListener(Events.CellMouseDown, new Listener<GridEvent<M>>() {
                public void handleEvent(GridEvent<M> e) {
                    handleCellMouseDown(e);
                }
            });
        }

        componentPluginInitialized = true;
    }

    /**
     * Sets the contextual renderer for this renderer.
     * <p>
     * This method is valuable only when flag {@link #isInContextualRenderer()} returns <code>true</code>.
     * 
     * @param renderer
     *            the contextualRenderer to set.
     */
    public void setContextualRenderer(ContextualWidgetGridCellRenderer<M> renderer) {
        if (renderer != null && renderer != this && isInContextualRenderer()) {
            this.contextualRenderer = renderer;
        }
    }

    /**
     * Notifies all registered listeners about 'change' event on given component.
     * 
     * @param model
     *            Affected model.
     * @param property
     *            Affected property in given model.
     * @param modelRecord
     *            The record corresponding to given model.
     * @param sourceComponent
     *            Source component firing this event.
     * @param event
     *            Fired event.
     * @param sourceRenderer
     *            The renderer firing this event. If current renderer is a
     *            {@link ContextualWidgetGridCellRenderer}, this source renderer is the inner renderer at
     *            current context.
     */
    protected void notifyValueChanged(M model, String property, Record modelRecord, Component sourceComponent,
            FieldEvent event, WidgetGridCellRenderer<M> sourceRenderer) {
        if (isInContextualRenderer()) {
            this.contextualRenderer.notifyValueChanged(model, property, modelRecord, sourceComponent, event,
                    sourceRenderer);
        } else {
            GridEvent<M> gridEvent = new GridEvent<M>(ownerGrid);
            gridEvent.setModel(model);
            gridEvent.setProperty(property);
            gridEvent.setRecord(modelRecord);
            gridEvent.setValue((Object) model.get(property));
            gridEvent.setComponent(sourceComponent);
            gridEvent.setSource(sourceRenderer);

            for (Listener<GridEvent<M>> listener : changeEventListener) {
                listener.handleEvent(gridEvent);

                // stop broadcasting this event to next listeners
                if (gridEvent.isCancelled()) {
                    break;
                }
            }
        }
    }
}
